﻿using Microsoft.Extensions.Options;

using EOH.Sms.Alibaba.Req;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace EOH.Sms.Alibaba
{
    /// <summary>阿里巴巴短信服务</summary>
    public class AlibabaService : IService
    {
        #region 属性
        IHttpClientFactory _HttpClientFactory;
        AlibabaOptions _AlibabaOptions;
        static string _HttpClientFactoryName = typeof(AlibabaService).FullName;
        Dictionary<string, string> _CommParam;
        #endregion

        #region 构造函数
        /// <summary>构造函数</summary>
        public AlibabaService(IHttpClientFactory httpClientFactory, IOptions<AlibabaOptions> options)
        {
            _HttpClientFactory = httpClientFactory;
            _AlibabaOptions = options.Value;
            _CommParam ??= _AlibabaOptions.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(_AlibabaOptions)?.ToString());
            _CommParam.Remove(nameof(AlibabaOptions.AccessKeySecret));
            _CommParam.Remove(nameof(AlibabaOptions.BaseAddressUrl));
        }
        #endregion
        public Task<TResponse> Execute<TResponse>(IRequest<TResponse> request)
        {
            if (_AlibabaOptions is null || string.IsNullOrWhiteSpace(_AlibabaOptions.AccessKeyId) || string.IsNullOrWhiteSpace(_AlibabaOptions.AccessKeySecret))
            { throw new Exception("AlibabaOptions 配置参数有误"); }
            if (request is AlibabaRequest<TResponse> alibabarequest)
            {
                var param = BuildParam(alibabarequest);
                return Send(request, param);
            }
            else { throw new Exception("request 非阿里接口参数实体"); }
        }

        public Task<TResponse> Execute<TModel, TResponse>(IRequest<TModel, TResponse> request)
        {
            if (_AlibabaOptions is null || string.IsNullOrWhiteSpace(_AlibabaOptions.AccessKeyId) || string.IsNullOrWhiteSpace(_AlibabaOptions.AccessKeySecret))
            { throw new Exception("AlibabaOptions 配置参数有误"); }
            if (request is AlibabaRequest<TModel, TResponse> alibabarequest)
            {
                var param = BuildParam(alibabarequest, request.Model);
                return Send(request, param);
            }
            else { throw new Exception("request 非阿里接口参数实体"); }
        }

        #region 辅助函数
        async Task<TResponse> Send<TResponse>(IRequest<TResponse> request, string param)
        {
            try
            {
                using var client = _HttpClientFactory.CreateClient(_HttpClientFactoryName);
                using var alibabaRequest = new HttpRequestMessage(request.Method,  HttpMethod.Post == request.Method ? string.Empty : $"?{param}");
                if (HttpMethod.Post == request.Method) { alibabaRequest.Content = new StringContent(param, Encoding.UTF8, "application/x-www-form-urlencoded"); }
                var response = await client.SendAsync(alibabaRequest);
                var content = await response.Content.ReadAsStringAsync();
                return request.ConvertResponse(content);
            }
            catch (Exception ex) { throw ex; }
        }
        string BuildParam<TResponse>(AlibabaRequest<TResponse> request, object model = null) {
            // ************* 步骤 1：拼接规范请求串 *************
            var param = new Dictionary<string, string>(_CommParam);
            param.Add("SignatureNonce", Guid.NewGuid().ToString("N"));
            param.Add("Timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"));
            param.Add(nameof(request.Action), request.Action);
            var properties = model?.GetType()?.GetProperties();
            if (null != properties)
            {
                foreach (var property in properties)
                {
                    var data = property.GetValue(model);
                    if (data is null) { continue; }
                    if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)) //判断是否IList
                    {
                        if (request.GetType() == typeof(SendSmsRequest)) { param.Add(property.Name, string.Join(",", (data as IEnumerable).Cast<string>().ToArray())); }
                        else { param.Add(property.Name, JsonSerializer.Serialize(data)); }
                    }
                    else { param.Add(property.Name, $"{data}"); }
                }
            }
            /* ************** 步骤 2：计算签名 *************
            * 参数排序。按照参数首字母的字典顺序对参数排序，字典序大写字母在小写字母前面，排序参数包括
            * 公共请求参数和接口自定义参数（即 OpenAPI 文档中的请求参数），不包括公共请求参数中的Signature
            */
            var urlParam = string.Join("&", param.OrderBy(x => x.Key, StringComparer.Ordinal).Select(x => $"{WebUtility.UrlEncode(x.Key)}={WebUtility.UrlEncode(x.Value)}"));
            var sign = BuildSign($"{request.Method}&{WebUtility.UrlEncode("/")}&{WebUtility.UrlEncode(urlParam)}");
            //加密数据后，sign还需要UrlEncode
            urlParam = string.Concat(urlParam, "&Signature=", WebUtility.UrlEncode(sign));
            return urlParam;
        }

        string BuildSign(string content)
        {
            //加密说明 https://help.aliyun.com/zh/sdk/product-overview/rpc-mechanism?spm=a2c4g.419298.0.0.b7922efbLmNnbm#title-v43-umy-pqm
            using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes($"{_AlibabaOptions.AccessKeySecret}&"));
            var val = hmac.ComputeHash(Encoding.UTF8.GetBytes(content));
            return Convert.ToBase64String(val);
        }
        #endregion
    }
}
